package Rangel.common.world.entity;

import Rangel.common.pathplanning.astar.graph.MyEdge;
import Rangel.common.world.helper.RangelWorldHelper;
import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import adf.agent.module.ModuleManager;
import Rangel.common.RangelConstants;
import Rangel.common.world.tools.Util;
import Rangel.common.pathplanning.astar.graph.GraphModule;
import Rangel.common.pathplanning.astar.graph.Node;
import rescuecore2.misc.Pair;
import rescuecore2.misc.geometry.Line2D;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

import java.awt.*;
import java.util.*;
import java.util.List;


public class RangelRoad {

    private Map<EntityID, List<Polygon>> buildingVisitableParts;//建筑可访问部分？
    private Map<RangelEdge, HashSet<RangelEdge>> reachableEdges;//可达的边缘
    private List<RangelBlockade> RangelBlockades;//障碍
    private List<RangelEdge> passableRangelEdges;//是否是可通行的边
    private Set<RangelEdge> blockedEdges;//障碍的边
    private HashSet<RangelEdge> openEdges;//开放的边缘
    private List<Point2D> apexPoints;//顶点
    private List<RangelEdge> RangelEdges;//边缘
    private boolean isReachable;//是否可以到达
    private int totalRepairCost;//总清理消耗
    private boolean isPassable;//是否课通行？
    private int lastSeenTime;//上次查看时间
    private int lastUpdateTime;//最后更新时间
    private int lastResetTime;//上次重置时间
    private List<Path> paths;//路径

    private Polygon polygon;//多边形

    private boolean isSeen;//被看见
    private int repairTime;//清理时间
    private Road parent;//底层的道路类
    private Set<EntityID> observableAreas;//可观察区域


    private WorldInfo worldInfo;
    private AgentInfo agentInfo;
    private ScenarioInfo scenarioInfo;
    RangelWorldHelper worldHelper;


    public RangelRoad(AgentInfo agentInfo, WorldInfo worldInfo, ScenarioInfo scenarioInfo, ModuleManager moduleManager, Road parent, RangelWorldHelper worldHelper) {
        this.worldInfo = worldInfo;
        this.agentInfo = agentInfo;
        this.scenarioInfo = scenarioInfo;
        this.worldHelper = worldHelper;
        this.parent = parent;
        initialize(parent, createRangelEdges(parent.getEdges()));
    }

    private void initialize(Road road, List<RangelEdge> RangelEdges) {
        setObservableAreas(new HashSet<>());
        this.parent = road;
        this.apexPoints = new ArrayList<>();
        this.RangelBlockades = new ArrayList<>();
        this.blockedEdges = new HashSet<>();
        this.reachableEdges = new HashMap<>();
        paths = new ArrayList<>();
        this.isPassable = true;
        this.isReachable = true;
        this.lastSeenTime = 0;
        this.totalRepairCost = 0;
        this.repairTime = 0;
        lastResetTime = 0;
        lastUpdateTime = 0;
        passableRangelEdges = new ArrayList<>();
        this.buildingVisitableParts = new HashMap<>();
        for (RangelEdge RangelEdge : RangelEdges) {
            if (RangelEdge.isPassable()) {
                passableRangelEdges.add(RangelEdge);
            }
        }

        for (Path p : worldHelper.getPaths()) {
            if (p.contains(road)) {//将包含该道路的路径加入到paths中
                paths.add(p);
            }
        }
        setSeen(false);//未被走过

        setRangelEdges(RangelEdges);
        this.openEdges = new HashSet<>(RangelEdges);
        resetReachableEdges();
    }

    public void update() {//更新道路信息
        reset();
        setRangelBlockades();
        RangelEdges.forEach(RangelEdge -> {//每个边的可行性
            if (RangelEdge.isPassable()) {
                RangelEdge.setOpenPart(RangelEdge.getLine());
                List<RangelBlockade> blockedStart = new ArrayList<>();
                List<RangelBlockade> blockedEnd = new ArrayList<>();
                for (RangelBlockade RangelBlockade : RangelBlockades) {
                    if (Util.distance(RangelBlockade.getPolygon(), RangelEdge.getStart()) < RangelConstants.AGENT_PASSING_THRESHOLD) {
                        blockedStart.add(RangelBlockade);
                    }
                    if (Util.distance(RangelBlockade.getPolygon(), RangelEdge.getEnd()) < RangelConstants.AGENT_PASSING_THRESHOLD) {
                        blockedEnd.add(RangelBlockade);
                    }
                }
                setRangelEdgeOpenPart(RangelEdge);
                if (RangelBlockades.size() == 1) {
                    if (Util.containsEach(blockedEnd, blockedStart)) {
                        RangelBlockades.get(0).addBlockedEdges(RangelEdge);
                        RangelEdge.setBlocked(true);
                        RangelEdge.setAbsolutelyBlocked(true);
                    }
                } else {
                    for (RangelBlockade block1 : blockedStart) {
                        for (RangelBlockade block2 : blockedEnd) {
                            if (Util.isPassable(block1.getPolygon(), block2.getPolygon(), RangelConstants.AGENT_PASSING_THRESHOLD)) {
                                RangelEdge.setBlocked(true);
                                block1.addBlockedEdges(RangelEdge);
                                block2.addBlockedEdges(RangelEdge);
                            }
                        }
                    }
                }
                if (RangelEdge.isBlocked()) {
                    blockedEdges.add(RangelEdge);
                }
                isPassable = getReachableEdges(RangelEdge) != null && !getReachableEdges(RangelEdge).isEmpty();
            } else {
                for (RangelBlockade RangelBlockade : RangelBlockades) {
                    double distance = Util.distance(RangelEdge.getLine(), RangelBlockade.getPolygon());

                    if (distance < RangelConstants.AGENT_PASSING_THRESHOLD) {
                        RangelEdge.setBlocked(true);
                        RangelBlockade.addBlockedEdges(RangelEdge);
                    }
                }
            }
        });

        checkTooSmallEdgesPassably();
        if (agentInfo.me() instanceof Human) {
            for (RangelEdge RangelEdge : passableRangelEdges) {
                if (!RangelEdge.isBlocked() && !RangelEdge.isTooSmall()) {
                    if (Util.lineLength(RangelEdge.getOpenPart()) < (RangelConstants.AGENT_PASSING_THRESHOLD)) {
                        blockedEdges.add(RangelEdge);
                        RangelEdge.setBlocked(true);
                    }
                }
            }
        }

        updateRepairCost();
        openEdges.removeAll(blockedEdges);
        if (!(agentInfo.me() instanceof PoliceForce)) {
            updateNodesPassably();
        }
        lastUpdateTime = agentInfo.getTime();
    }

    private void checkTooSmallEdgesPassably() {

        for (RangelEdge RangelEdge : passableRangelEdges) {
            if (RangelEdge.isTooSmall()) {
                Set<EntityID> neighbours = new HashSet<>(parent.getNeighbours());
                neighbours.addAll(((Area) worldInfo.getEntity(RangelEdge.getNeighbours().second())).getNeighbours());
                RangelRoad RangelRoad;
                FOR1:
                for (EntityID neighbourID : neighbours) {
                    RangelRoad = getRangelRoad(neighbourID);
                    if (RangelRoad != null) {
                        for (RangelBlockade RangelBlockade : RangelRoad.getRangelBlockades()) {
                            if (Util.distance(RangelBlockade.getPolygon(), RangelEdge.getMiddle()) < RangelConstants.AGENT_PASSING_THRESHOLD) {
                                blockedEdges.add(RangelEdge);
                                RangelEdge.setBlocked(true);
                                break FOR1;
                            }
                        }
                    }
                }
            }
        }
    }

    private RangelRoad getRangelRoad(EntityID id) {
        return worldHelper.getRangelRoad(id);
    }


    private void updateNodesPassably() {
        if (!(agentInfo.me() instanceof Human)) {
            return;
        }
        GraphModule graph = getGraph();
        for (RangelEdge RangelEdge : passableRangelEdges) {
            Node node = graph.getNode(RangelEdge.getMiddle());
            if (node == null) {
                continue;
            }

            if (RangelEdge.isBlocked() || RangelEdge.isOtherSideBlocked(worldInfo)) {
                node.setPassable(false, agentInfo.getTime());
            } else {
                node.setPassable(true, agentInfo.getTime());
            }
        }
    }

    private GraphModule getGraph() {
        return worldHelper.getGraph();
    }

    public void addBuildingVisitableParts(EntityID buildingID, Polygon visitablePartsPolygon) {
        if (!buildingVisitableParts.containsKey(buildingID)) {
            buildingVisitableParts.put(buildingID, new ArrayList<>());
        }
        buildingVisitableParts.get(buildingID).add(visitablePartsPolygon);
    }

    private void setRangelEdgeOpenPart(RangelEdge RangelEdge) {
        Point2D p1 = null, p2 = null;
        int d1 = 0, d2 = 0;
        for (RangelBlockade RangelBlockade : RangelBlockades) {
            List<Point2D> pointList = Util.getPoint2DList(RangelBlockade.getPolygon().xpoints, RangelBlockade.getPolygon().ypoints);
            List<Point2D> centerPoints = new ArrayList<>();
            boolean isBlockedStart = false, isBlockedEnd = false;
            for (Point2D point : pointList) {
                if (Util.contains(RangelEdge.getLine(), point, 100)) {
                    if (Util.distance(point, RangelEdge.getLine().getOrigin()) <= 10) {
                        isBlockedStart = true;
                    } else if (Util.distance(point, RangelEdge.getLine().getEndPoint()) <= 10) {
                        isBlockedEnd = true;
                    } else {
                        centerPoints.add(point);
                    }
                }
            }

            for (Point2D centerPoint : centerPoints) {
                if (isBlockedEnd && isBlockedStart) {
                    p1 = RangelEdge.getMiddle();
                    p2 = RangelEdge.getMiddle();
                    break;
                } else if (isBlockedEnd) {
                    int dist = Util.distance(centerPoint, RangelEdge.getLine().getEndPoint());
                    if (dist > d2) {
                        p2 = centerPoint;
                        d2 = dist;
                    }
                } else if (isBlockedStart) {
                    int dist = Util.distance(centerPoint, RangelEdge.getLine().getOrigin());
                    if (dist > d1) {
                        p1 = centerPoint;
                        d1 = dist;
                    }
                }
            }
        }
        if (p1 == null) {
            p1 = RangelEdge.getStart();
        }
        if (p2 == null) {
            p2 = RangelEdge.getEnd();
        }
        RangelEdge otherSide = RangelEdge.getOtherSideEdge(worldInfo);
        Line2D openPart = new Line2D(p1, p2);
        if (otherSide != null) {
            RangelRoad neighbour = getRangelRoad(otherSide.getNeighbours().first());
            if (neighbour.getLastUpdateTime() >= this.lastUpdateTime) {
                Line2D otherSideOpenPart = otherSide.getOpenPart();
                if (Util.lineLength(openPart) < Util.lineLength(otherSideOpenPart)) {
                    RangelEdge.setOpenPart(openPart);
                    otherSide.setOpenPart(openPart);
                } else {
                    RangelEdge.setOpenPart(otherSideOpenPart);
                    otherSide.setOpenPart(otherSideOpenPart);
                }
            } else {
                RangelEdge.setOpenPart(openPart);
            }
        } else {
            RangelEdge.setOpenPart(openPart);
        }
    }

    public List<Path> getPaths() {
        return paths;
    }

    public HashSet<RangelEdge> getReachableEdges(RangelEdge from) {
        return reachableEdges.get(from);
    }

    public void addReachableEdges(RangelEdge from, RangelEdge to) {
        reachableEdges.get(from).add(to);
        reachableEdges.get(to).add(from);
    }

    public void removeReachableEdges(RangelEdge from, RangelEdge to) {
        reachableEdges.get(from).remove(to);
        reachableEdges.get(to).remove(from);
    }

    private void setApexPoint() {
        apexPoints.clear();
        for (RangelEdge RangelEdge : RangelEdges) {
            if (RangelEdge == null) {
                continue;
            }
            if (!apexPoints.contains(RangelEdge.getStart()))
                apexPoints.add(RangelEdge.getStart());
            else if (!apexPoints.contains(RangelEdge.getEnd()))
                apexPoints.add(RangelEdge.getEnd());
        }
        createPolygon();
    }

    public List<RangelEdge> getRangelEdgesTo(EntityID neighbourID) {
        List<RangelEdge> RangelEdgeList = new ArrayList<>();
        for (RangelEdge RangelEdge : RangelEdges) {
            if (RangelEdge.isPassable() && RangelEdge.getNeighbours().second().equals(neighbourID)) {
                RangelEdgeList.add(RangelEdge);
            }
        }
        return RangelEdgeList;
    }

    private void createPolygon() {
        int count = apexPoints.size();
        int[] xs = new int[count];
        int[] ys = new int[count];
        for (int i = 0; i < count; i++) {
            xs[i] = (int) apexPoints.get(i).getX();
            ys[i] = (int) apexPoints.get(i).getY();
        }
        polygon = new Polygon(xs, ys, count);
    }


    private void resetReachableEdges() {
        HashSet<RangelEdge> edgesInstead;
        for (RangelEdge RangelEdge : RangelEdges) {
            if (RangelEdge.isPassable()) {
                edgesInstead = new HashSet<>(passableRangelEdges);
                edgesInstead.remove(RangelEdge);
                reachableEdges.put(RangelEdge, edgesInstead);
            }
        }
    }

    public RangelEdge getEdgeInPoint(Point2D point) {
        for (RangelEdge RangelEdge : RangelEdges) {
            if (Util.contains(RangelEdge.getLine(), point, 1.0)) {
                return RangelEdge;
            }
        }
        return null;
    }

    public RangelEdge getRangelEdge(rescuecore2.standard.entities.Edge edge) {
        Point2D middle = Util.getPoint(getEdgeMiddle(edge));
        return getEdgeInPoint(middle);
    }


    private void setRangelEdges(List<RangelEdge> edges) {
        RangelEdges = new ArrayList<>();
        RangelEdges.addAll(edges);
        setApexPoint();
    }

    private List<RangelEdge> createRangelEdges(List<rescuecore2.standard.entities.Edge> edges) {
        List<RangelEdge> RangelEdges = new ArrayList<>();
        for (rescuecore2.standard.entities.Edge edge : edges) {
            RangelEdges.add(new RangelEdge(worldHelper, edge, parent.getID()));
        }
        return RangelEdges;
    }

    public Polygon getPolygon() {
        return polygon;
    }

    private void addBlockade(RangelBlockade blockade) {
        RangelBlockades.add(blockade);
    }


    private void setRangelBlockades() {
        totalRepairCost = 0;
        RangelBlockades.clear();
        if (!parent.isBlockadesDefined()) {
            return;
        }
        try {
            parent.getBlockades().forEach(blockID -> {
                StandardEntity entity = worldInfo.getEntity(blockID);
                if (entity instanceof Blockade) {
                    Blockade blockade = (Blockade) entity;
                    Polygon blockPolygon = Util.retainPolygon(getPolygon(), Util.getPolygon(blockade.getApexes()));
                    RangelBlockade newBlockade = new RangelBlockade(this, blockade, blockPolygon);
                    addBlockade(newBlockade);
                }
            });
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void updateRepairCost() {
        totalRepairCost = 0;
        for (RangelBlockade RangelBlockade : RangelBlockades) {
            totalRepairCost += RangelBlockade.getRepairCost();
        }

        int repairRate = scenarioInfo.getRawConfig().getIntValue("clear.repair.rate");
        repairTime = (int) Math.ceil(totalRepairCost / repairRate);
    }

    public void resetOldPassably() {
        if (notSeen() || agentInfo.me() instanceof PoliceForce || lastResetTime > lastUpdateTime) {
            return;
        }
        if (isTimeToReset()) {
            reset();
        }
    }

    private boolean isTimeToReset() {
        int resetTime = getRepairTime();
        resetTime += RangelConstants.ROAD_PASSABLY_RESET_TIME_IN_MEDIUM_MAP;
        return lastResetTime <= lastUpdateTime + resetTime && agentInfo.getTime() - lastSeenTime > resetTime;
    }

    public void reset() {
        blockedEdges.clear();
        openEdges.addAll(RangelEdges);
        RangelBlockades.clear();
        isPassable = true;
        isReachable = true;
        if (!(agentInfo.me() instanceof Human)) {
            return;
        }
        GraphModule graph = getGraph();
        for (RangelEdge RangelEdge : RangelEdges) {
            RangelEdge.setBlocked(false);
            RangelEdge.setAbsolutelyBlocked(false);
            RangelEdge otherEdge = RangelEdge.getOtherSideEdge(worldInfo);
            RangelEdge.setOpenPart(RangelEdge.getLine());
            if (otherEdge != null) {
                RangelRoad RangelRoad = getRangelRoad(RangelEdge.getNeighbours().second());
                if (RangelRoad.getLastUpdateTime() < lastUpdateTime) {
                    otherEdge.setOpenPart(otherEdge.getLine());
                }
            }
            Area neighbour = (Area) worldInfo.getEntity(RangelEdge.getNeighbours().second());
            if (RangelEdge.isPassable()) {
                Node node = graph.getNode((RangelEdge.getMiddle()));
                if (node == null) {
                    continue;
                }
                if (neighbour instanceof Road) {
                    RangelRoad RangelRoad = getRangelRoad(neighbour.getID());
                    RangelEdge neighbourEdge = RangelRoad.getEdgeInPoint(RangelEdge.getMiddle());
                    if (neighbourEdge != null && !neighbourEdge.isBlocked()) {
                        node.setPassable(true, agentInfo.getTime());
                    }
                } else {
                    node.setPassable(true, agentInfo.getTime());
                }
            }
        }
        resetReachableEdges();
        for (MyEdge myEdge : graph.getMyEdgesInArea(getID())) {
            myEdge.setPassable(true);
        }
        lastResetTime = agentInfo.getTime();
    }

    public List<RangelBlockade> getRangelBlockades() {
        return RangelBlockades;
    }

    public boolean isNeedUpdate() {
        if (!(agentInfo.me() instanceof Human)) {
            return false;
        }
        if (!parent.isBlockadesDefined()) {
            return true;
        }
        if (agentInfo.me() instanceof PoliceForce ||
                (parent.getBlockades().size() != getRangelBlockades().size()) ||
                lastSeenTime == 0 ||
                lastSeenTime < agentInfo.getTime() - 10) {
            return true;
        }
        Blockade blockade;
        for (RangelBlockade RangelBlockade : getRangelBlockades()) {
            blockade = RangelBlockade.getParent();
            if (blockade == null || !parent.getBlockades().contains(RangelBlockade.getParent().getID()) || blockade.getRepairCost() != RangelBlockade.getRepairCost()) {
                return true;
            }
        }
        return false;
    }

    public List<RangelEdge> getRangelEdges() {
        return RangelEdges;
    }

    public Road getParent() {
        return parent;
    }

    public int getLastUpdateTime() {
        return lastUpdateTime;
    }

    public boolean isPassable() {
        return isPassable;
    }

    public EntityID getID() {
        return parent.getID();
    }

    public boolean isReachable() {
        return isReachable;
    }

    public void setReachable(boolean reachable) {
        isReachable = reachable;
    }

    public void setSeen(boolean seen) {
        this.isSeen = seen;
    }

    public boolean notSeen() {
        return !isSeen;
    }

    public void setLastSeenTime(int lastSeenTime) {
        this.lastSeenTime = lastSeenTime;
    }

    public int getLastSeenTime() {
        return lastSeenTime;
    }

    public int getRepairTime() {
        return repairTime;
    }


    public Set<EntityID> getObservableAreas() {
        return observableAreas;
    }

    public void setObservableAreas(Set<EntityID> observableAreas) {
        this.observableAreas = observableAreas;
    }



    public static Pair<Integer, Integer> getEdgeMiddle(rescuecore2.standard.entities.Edge edge) {
        int x = (int) ((edge.getStartX() + edge.getEndX()) / 2.0);
        int y = (int) ((edge.getStartY() + edge.getEndY()) / 2.0);
        return new Pair<>(x, y);
    }

}
